#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "Menu.h"

#include "Class.h"
#include "OrdColl.h"
#include "String.h"
#include "Window.h"
#include "CollView.h"
#include "TextItem.h"
#include "Look.h"
#include "Box.h"

static int gMenuLevel;
static EvtHandler *gFirstMenuHandler;

//---- MenuButtonItem ----------------------------------------------------------

NewMetaImpl0(MenuButtonItem, StateButton);

MenuButtonItem::MenuButtonItem(int id, const char *l, const char *s)
													: StateButton(id, state)
{
	SetLabel(l);
	SetShortCut(s);
	SetFlag(eVObjVFixed|eVObjHFixed);
}

MenuButtonItem::MenuButtonItem(int id, VObject *l, const char *s)
													: StateButton(id, state)
{
	Add(l);
	SetShortCut(s);
	SetFlag(eVObjVFixed|eVObjHFixed);
}

Metric MenuButtonItem::GetMinSize()
{
	return gLook->MenuItemLayout()->GetMinSize(this);
}

void MenuButtonItem::SetOrigin(Point at)
{
	VObject::SetOrigin(at);
	gLook->MenuItemLayout()->SetOrigin(this, at);
}

void MenuButtonItem::SetExtent(Point e)
{
	CompositeVObject::SetExtent(e);
}

void MenuButtonItem::DrawInner(Rectangle, bool highlight)
{
	int code= state;
	if (state > 0)
		SETBIT(code, 2);
	if (highlight)
		SETBIT(code, 3);
	if (Enabled())
		SETBIT(code, 5);
	gLook->MenuItemLayout()->Adorn(this, contentRect, code);
}

const char *MenuButtonItem::AsString()
{
	return At(0)->AsString();
}

void MenuButtonItem::SetShortCut(const char *sc)
{
	if (sc) {
		const char *s= form("\327%c", sc[0]);
		if (Size() > 1) {
			TextItem *t= (TextItem*) At(1);
			t->SetString(s);
		} else
			Add(new TextItem(s));
	}
}

void MenuButtonItem::SetLabel(const char *s)
{
	if (s) {
		if (Size() > 0) {
			TextItem *t= (TextItem*) At(0);
			t->SetString(s);
		} else
			Add(new TextItem(s));
	}
}

void MenuButtonItem::SetString(const char *s)
{
	const char *cp= s;
	char buf1[100];
	int i;

	for (i= 0, cp= s; *cp && *cp != '@'; cp++, i++)
		buf1[i]= *cp;

	if (*cp == '@') {
		buf1[i]= 0;
		SetLabel(buf1);
		SetShortCut(&cp[1]);
	} else {
		SetLabel(s);
		SetShortCut(0);
	}
}

void MenuButtonItem::InputKbd(Token &t)
{
	if (Enabled() && Size() > 1) {
		TextItem *ti= (TextItem*) At(1);
		const char *s= ti->AsString();
		const u_char *up= gStdEncoding->UpperCaseMap();
		if (s && up[s[1]] == up[t.Code & 0x7f])
			Control(GetId(), cPartToggle, (void*) 1);
	}
}

int MenuButtonItem::Compare(Object *op)
{
	return strcmp(AsString(), op->AsString());
}

//---- MouseTracker ------------------------------------------------------------

class MouseTracker: public Command {
	VObject *last, *vop;
	Menu *menu;
	Rectangle exitRect;
public:
	MouseTracker(Menu *m, VObject *v, Rectangle er)
		{ menu= m; exitRect= er; vop= v; }
	void TrackFeedback(Point, Point, bool)
		{ }
	Command *TrackMouse(TrackPhase, Point, Point, Point);
};

Command *MouseTracker::TrackMouse(TrackPhase atp, Point, Point, Point np)
{
	Point pp= vop->GetPortPoint(np);
	if (pp.y < 0 && exitRect.IsNotEmpty() && !exitRect.ContainsPoint(pp))
	return gNoChanges;
	if (gMenuLevel > 1 && (pp.x < 0 || pp.y < 0))
	return gNoChanges;
		
	VObject *itemptr= menu->FindAnItem(np);

	switch (atp) {
	case eTrackPress:
		if (itemptr)
			itemptr->DoTrackMouse(eTrackPress, np);
		break;
	case eTrackMove:
		if (itemptr != last) {
			if (last)
				last->DoTrackMouse(eTrackExit, np);
			if (itemptr)
				itemptr->DoTrackMouse(eTrackPress, np);
			last= itemptr;
		} else if (itemptr)
			itemptr->DoTrackMouse(eTrackMove, np);
		break;
	case eTrackExit:
	case eTrackRelease:
		if (itemptr)
			itemptr->DoTrackMouse(atp, np);
		return gNoChanges;
	case eTrackIdle:
		break;
	}
	return this;
}

//---- Menu --------------------------------------------------------------------

NewMetaImpl(Menu,Manager, (TP(view), T(selection), TP(clipper), T(resetAll),
																	T(dimAll)));

Menu::Menu(const char *t, bool da, bool ra, int r, int c, SeqCollection *col)
																	: Manager(t)
{
	Init(cIdNone, t, da, ra, r, c, col);
}

Menu::Menu(int id, const char *t, bool da, bool ra, int r, int c,
											SeqCollection *col) : Manager(t)
{
	Init(id, t, da, ra, r, c, col);
}

Menu::~Menu()
{
	SafeDelete(view);
}

void Menu::Init(int id, const char*, bool da, bool ra, int r, int c,
														SeqCollection *col)
{
	if (1) {
		if (col == 0)
			col= new OrdCollection;
		view= new CollectionView(this, col, eCVDontStuckToBorder, r, c);
	} else
		view= new Box(cIdNone, Point(c,r), gPoint0, eVObjHExpand, col);
	view->ResetFlag(eVObjKbdFocus);
	selection= cIdNone;
	resetAll= ra;
	dimAll= da;
	SetId(id);
	nextHandler= 0;
}

void Menu::SetNextHandler(EvtHandler *eh)
{
	nextHandler= eh;
}

EvtHandler *Menu::GetNextHandler()
{
	return nextHandler;
}

VObject *Menu::FindAnItem(Point p)
{
	Iter next(GetCollection());
	register VObject *vop;

	while (vop= (VObject*) next())
		if (vop->ContainsPoint(p))
			return vop;
	return 0;
}

VObject *Menu::FindInitialItem(int id)
{
	VObject *vop= 0;
	if (id != cIdNone)
		if (vop= FindItem(id))
			return vop;
	return (VObject*) GetCollection()->First();
}

Point Menu::GetInitialPos(Point)
{
	return gPoint0;
}

void Menu::SetSelectedItem(int id)
{
	selection= id;
}

Window *Menu::DoMakeWindows()
{
	return
		new Window(this,
			clipper= new Clipper(view, cAutoSize, cIdNone, gInkNone),
			eWinPulldownMenu,
			eWinDefault,
			cAutoSize,
			GetName()
		);
}

int Menu::CheckKey(Token &t)
{
	if (view && view->IsKindOf(CollectionView))
		((CollectionView*)view)->Update();

	if (resetAll)
		ResetAll();
	if (dimAll)
		DisableAll();

	if (gFirstMenuHandler == 0) {
		Manager *m= (Manager*) Manager::GetNextHandler();
		if (m)
			gFirstMenuHandler= m->GetFirstHandler();
	}
	if (gFirstMenuHandler)
		gFirstMenuHandler->DoSetupMenu(this);

	if (GetCollection()->Size() <= 0) {
		gFirstMenuHandler= 0;
		return cIdNone;
	}

	Iter next(GetCollection());
	register VObject *vop;

	int oldselection= selection;
	selection= cIdNone;

	while (vop= (VObject*) next())
		if (vop->IsKindOf(MenuButtonItem)) {
			((MenuButtonItem*)vop)->InputKbd(t);
			if (selection != cIdNone)
				break;
		}

	gFirstMenuHandler= 0;

	if (selection == cIdNone) {
		selection= oldselection;
		return cIdNone;
	}

	return selection;
}

extern Point showdelta;

int Menu::Popup(Point p, VObject *fp, int id, bool showlast,
												int menupos, Rectangle exitr)
{
	if (resetAll)
		ResetAll();
	if (dimAll)
		DisableAll();
		
	if (gFirstMenuHandler == 0) {
		Manager *m= fp->GetManager();
		if (m)
			gFirstMenuHandler= m->GetFirstHandler();
	}

	if (gFirstMenuHandler)
		gFirstMenuHandler->DoSetupMenu(this);

	if (GetCollection()->Size() <= 0) {
		gFirstMenuHandler= 0;
		return cIdNone;
	}

	selection= id;

	MakeWindows();

	if (TestFlag(eMenuNoScroll)) {
		clipper->SetMinExtent(cAutoSize);
	} else {
		Point e= view->GetMinSize().extent;
		if (view->IsKindOf(CollectionView)) {
			Rectangle r(((CollectionView*)view)->ItemRect(2,20));
			clipper->SetMinExtent(r.origin+r.extent);
		} else
			clipper->SetMinExtent(Min(Point(200), e));
		clipper->Scroll(cPartScrollAbs, gPoint0, FALSE);
	}

	if (view->IsKindOf(View))
		((View*)view)->ClearSelection(FALSE);

	Point lp;
	clipper->
	GetWindow()->GetMinSize();
	VObject *vop= FindInitialItem(selection);
	if (vop) {
		if (menupos)
			lp= vop->contentRect.origin;
		else
			lp= vop->contentRect.W()+Point(4,0);
	}

	int oldselection= selection;
	selection= cIdNone;

	Point pp= view->GetPortPoint(lp);
	if (pp == gPoint0) {    // this is a hack
		pp= gPoint2;
	}

	OpenAt(fp, p - pp + Point(2,3), TRUE, FALSE);

	gMenuLevel++;
	if (!showlast)
		lp= gPoint_1;

	exitr.origin-= showdelta;
	view->TrackInContent(lp, Token(), new MouseTracker(this, view, exitr));
	gMenuLevel--;
	Close();

	if (gMenuLevel == 0)
		gFirstMenuHandler= 0;

	if (selection == cIdNone) {
		selection= oldselection;
		return cIdNone;
	}

	return selection;
}

int Menu::Pulldown(Point p, VObject *vop)
{
	return Popup(p, vop, selection, TRUE, FALSE, gRect0);
}

void Menu::Control(int id, int part, void*)
{
	if (part == cPartToggle)
		selection= id;
}

OStream& Menu::PrintOn(OStream &s)
{
	Manager::PrintOn(s);
	return s << view SP << selection SP << resetAll SP << dimAll SP;
}

IStream& Menu::ReadFrom(IStream &s)
{
	Manager::ReadFrom(s);
	return s >> view >> selection >> resetAll >> dimAll;
}

SeqCollection *Menu::GetCollection()
{
	if (view->IsKindOf(CollectionView))
		return ((CollectionView*)view)->GetCollection();
	if (view->IsKindOf(CompositeVObject))
		return ((CompositeVObject*)view)->GetList();
	return 0;
}

void Menu::SetCollection(SeqCollection *c, bool freeold)
{
	if (view->IsKindOf(CollectionView))
		((CollectionView*)view)->SetCollection(c, freeold);
}

void Menu::Insert(VObject *mip)
{
	GetCollection()->AddFirst(mip);
}

void Menu::InsertItem(const char *s, int id)
{
	Insert(MakeMenuItem(s, id));
}

void Menu::Append(VObject *mip)
{
	GetCollection()->Add(mip);
}

VObject *Menu::FindItem(int id)
{
	Iter next(GetCollection());
	register VObject *vop;

	while (vop= (VObject*) next())
		if (vop->GetId() == id)
			return vop;
	return 0;
}

void Menu::InsertBefore(int id, VObject *mip)
{
	VObject *vop= FindItem(id);

	if (vop)
		GetCollection()->AddBeforePtr(mip, vop);
	else
		GetCollection()->AddFirst(mip);
}

void Menu::InsertAfter(int id, VObject *mip)
{
	VObject *vop= FindItem(id);

	if (vop)
		GetCollection()->AddAfterPtr(mip, vop);
	else
		GetCollection()->Add(mip);
}

void Menu::InsertItemAfter(int atId, const char *s, int id)
{
	InsertAfter(atId, MakeMenuItem(s, id));
}

void Menu::InsertItemBefore(int atId, const char *s, int id)
{
	InsertBefore(atId, MakeMenuItem(s, id));
}

void Menu::AppendItem(const char *s, int id)
{
	GetCollection()->Add(MakeMenuItem(s, id));
}

void Menu::AppendItems(const char *va_(first), ...)
{
	va_list ap;
	va_start(ap,va_(first));
	InsertVItems(cIdNone, FALSE, va_(first), ap);
	va_end(ap);
}

void Menu::InsertItemsBefore(int va_(atId), ...)
{
	va_list ap;
	va_start(ap,va_(atId));
	char *first= va_arg(ap, char*);
	InsertVItems(va_(atId), TRUE, first, ap);
	va_end(ap);
}

void Menu::InsertItemsAfter(int va_(atId), ...)
{
	va_list ap;
	va_start(ap,va_(atId));
	char *first= va_arg(ap, char*);
	InsertVItems(va_(atId), FALSE, first, ap);
	va_end(ap);
}

VObject *Menu::MakeMenuItem(const char *s, int id)
{
	if (strcmp(s, "-") == 0)
		return new Separator;
	MenuButtonItem *mi= new MenuButtonItem(id, "");
	mi->SetString(s);
	return mi;
}

void Menu::InsertVItems(int atId, bool before, const char *first, va_list ap)
{
	SeqCollection *col= GetCollection();
	int id= cIdNone;
	VObject *vop, *atvop= 0;

	if (atId != cIdNone)
		atvop= FindItem(atId);

	for (int i= 0;; i++) {
		const char *s= (i == 0) ? first : va_arg(ap, char*);
		if (s == 0)
			break;
		if (strcmp(s, "-"))
			id= va_arg(ap, int);
		vop= MakeMenuItem(s, id);
		if (atvop == 0)
			col->Add(vop);
		else if (before && i== 0)
			col->AddBeforePtr(vop, atvop);
		else
			col->AddAfterPtr(vop, atvop);
		atvop= vop;
	}
}

void Menu::EnableAll(bool b)
{
	GetCollection()->ForEach(VObject,Enable)(b, FALSE);
}

void Menu::ResetAll()
{
	Iter next(GetCollection());
	Object *op;
	while (op= next())
		if (op->IsKindOf(StateButton))
			((StateButton*)op)->SetValue(0,FALSE);
}

void Menu::CheckItem(int id, int state, int type)
{
	VObject *vop= FindItem(id);
	if (vop) {
		StateButton *mi= Guard(vop, StateButton);
		if (state)
			mi->SetValue(type, FALSE);
		else
			mi->SetValue(0, FALSE);
	}
}

void Menu::ReplaceItem(int id, const char *s)
{
	VObject *v= FindItem(id);

	if (v) {
		if (v->IsKindOf(TextItem)) {
			TextItem *t= (TextItem*) v;
			t->SetString(s);
		} else if (v->IsKindOf(MenuButtonItem)) {
			MenuButtonItem *mi= (MenuButtonItem*)v;
			mi->SetString(s);
		}
		if (view->IsKindOf(CollectionView))
			((CollectionView*)view)->Modified();
	}
}

void Menu::ToggleItem(int id, bool toggle, const char *toggleTrue,
														const char *toggleFalse)
{
	ReplaceItem(id, toggle ? toggleTrue : toggleFalse);
}

void Menu::ToggleItem(int id, bool toggle, const char *fmt,
								const char *toggleTrue, const char *toggleFalse)
{
	ReplaceItem(id, form(fmt, toggle ? toggleTrue : toggleFalse));
}

VObject *Menu::RemoveItem(int id)
{
	VObject *vop= FindItem(id);
	if (vop) {
		VObject *cont= vop->GetContainer();
		Menu *m= this;
		if (cont && cont->IsKindOf(Menu))
			m= (Menu*)cont;
		m->GetCollection()->RemovePtr(vop);
	}
	return vop;
}

void Menu::RemoveAllItems(bool free)
{
	// view->ClearSelection();
	Collection *cp= GetCollection();
	if (free)
		cp->FreeAll();
	cp->RemoveAll(cp);
}

Menu *Menu::FindMenuItem(int id)
{
	VObject *vop= FindItem(id);
	if (!vop || !vop->IsKindOf(MenuItem))
		return 0;
	MenuItem *mip= (MenuItem*)vop;
	return mip->MyMenu();
}

void Menu::EnableItem(int id, bool b)
{
	VObject *vop= FindItem(id);
	if (vop)
		vop->Enable(b, FALSE);
}

void Menu::EnableItems(int va_(first), ...)
{
	va_list ap;
	va_start(ap,va_(first));
	register int id;

	EnableItem(va_(first));
	while (id= va_arg(ap, int))
		EnableItem(id);
	va_end(ap);
}

void Menu::AppendMenu(Menu *m, int id)
{
	Append(new MenuItem(id, new TextItem(m->GetName()), m));
}

